// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //

package main

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"sync"

	_ "github.com/go-sql-driver/mysql"
)

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //

var (
	dbConn       *sql.DB
	dbOnce       sync.Once
	dbConnParams string = fmt.Sprintf(
		"%s:%s@tcp(%s)/%s",
		os.Getenv("DB_USER"),
		os.Getenv("DB_PASS"),
		os.Getenv("DB_HOST"),
		os.Getenv("DB_NAME"),
	)
)

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //

// programer is data structure  to
// describe one of the top 10 most
// famous computer programmers  of
// all time,  from  redit opinions
type programer struct {
	Id                               int
	FirstName, LastName, Description string
}

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //

// getDbConn  is   function   to
// create / fetch 	instance  of
// the MySQL database connection
//
// getDbConn function  has  behavior  of
// the singleton (creational programming
// pattern,   more 	  about  programming
// patterns: https://sourcemaking.com/design_patterns/creational_patterns)
func getDbConn() (*sql.DB, error) {

	dbOnce.Do(func() {
		c, cErr := sql.Open("mysql", dbConnParams)
		if cErr != nil {
			log.Println(
				`
				creation connection with
				MySQL   database  failed
				`,
			)
			panic(cErr)
		}
		dbConn = c
	})

	if pErr := dbConn.Ping(); pErr != nil {
		log.Println(
			`
			checking   connection   with
			MySQL    database     failed
			`,
			pErr,
		)
		return nil, pErr
	}

	return dbConn, nil

}

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //

// index is private function  to define
// the web server handler for "/" route (known as index)
//
// it reads from environmental storage name of
// the web server owner and returns to  client
// the string like:
// "Hello! You are on <owner_name>'s web server!"
func index(w http.ResponseWriter, r *http.Request) {

	var o string = os.Getenv("OWNER")

	w.Write([]byte(fmt.Sprintf("Hello! You are on %s's web server!", o)))

}

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //

// read is function to read full
// table from the MySQL database
//
// no arguments required
// returns rows into json format
func read(w http.ResponseWriter, r *http.Request) {

	var p []programer

	// create /  fetch  instance  of
	// the MySQL database connection
	c, cErr := getDbConn()
	if cErr != nil {
		w.WriteHeader(500)
		w.Write([]byte("invalid instance of the MySQL database connection"))
		return
	}

	// execute select statement into
	// MySQL database to  fetch  all
	// rows from  programmers  table
	q, qErr := c.Query("SELECT id,  first_name,  last_name, description FROM programmers")
	if qErr != nil {
		w.WriteHeader(500)
		w.Write([]byte("fetching rows from programers MySQL  table failed"))
		return
	}
	defer q.Close()

	// iterate over fetched rows and
	// dump result to the programmer
	// data 			   structure
	for q.Next() {
		var i programer
		if sErr := q.Scan(&i.Id, &i.FirstName, &i.LastName, &i.Description); sErr != nil {
			w.WriteHeader(500)
			w.Write([]byte("parsing  results  of  the  SQL  query  failed"))
			return
		}
		p = append(p, i)
	}

	// marshalling fetched  list  of
	// programmers into  json format
	m, mErr := json.Marshal(p)
	if mErr != nil {
		w.WriteHeader(500)
		w.Write([]byte("marshaling  list of programmers  to  json  failed"))
		return
	}

	w.WriteHeader(200)
	w.Write(m)

}

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //

// dbHealth is function to  check  if
// MySQL database connection has been
// successfully 		      created
func dbHealth(w http.ResponseWriter, r *http.Request) {

	_, cErr := getDbConn()
	if cErr != nil {
		w.WriteHeader(500)
		w.Write([]byte("MySQL database connection HAS NOT BEEN created" + cErr.Error()))
		return
	}

	w.WriteHeader(200)
	w.Write([]byte("MySQL database connection has been successfully created"))

}

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //

// main  is  private function
// to start program main loop
func main() {

	http.HandleFunc("/", index)
	http.HandleFunc("/programmers", read)
	http.HandleFunc("/db_health", dbHealth)

	http.ListenAndServe(":555", nil)

}

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
